function Cell(x, y) {
    this.x = x;
    this.y = y;
}
Cell.prototype.toString = function () {return '(' + this.x + ',' + this.y + ')';};

View.prototype.supportTouch = ("ontouchstart" in window);
function View() {

    this.setOnResizeListener = function(listener){
        window.onresize = this.onResize;
        this.onReSizeListener = listener;
    };

    this.onResize =()=>{
        this.autoConfigSize();
        this.onReSizeListener(this.config.width, this.config.height);
    };

    this.loadConfig = function(config){
        this.config = {};
        this.config.width = config.width;
        this.config.height = config.height;
        this.config.cellSize = config.cell_size || 20;
        this.config.borderWidth = config.border_width;
        this.config.color = {};
        this.config.color.all = config.g_color;
        this.config.color.background = config.background_color;
        this.config.color.default = config.default_color;
        this.config.color.border = config.border_color;
    };

    this.grid = function () {
        return this.model.grid();
    };

    this.setModel = function(model){
        this.model = model;
    };

    this.setDrawGrad = function(func){
        this.gradDraw = func;
    }

    this.snapShot = function(grid){
        if(typeof grid !== 'undefined'){
            this.displayed_grid = grid.copy();
        }
        return this.displayed_grid;
    };

    this.syncGrid = function(x, y){
        if(this.displayed_grid != undefined){
            this.displayed_grid[x][y] = {};
            this.displayed_grid[x][y].color = this.model.cell(x, y, 'color');
        }
    };

    this.getCellAtEventPosition = function(event) {
        if(View.prototype.supportTouch){
            event = event.originalEvent.targetTouches[0]
        }
        let grid = $('#grid');
        let px = (event.pageX - grid.offset().left);
        let py = (event.pageY - grid.offset().top);
        let cellOffset = this.config.cellSize + this.config.borderWidth;
        let x = Math.floor(px / cellOffset);
        let y = Math.floor(py / cellOffset);
        if ((x < 0) || (y < 0) ||
            (x >= this.config.width) || (y >= this.config.height)) {
            return null;
        }

        return new Cell(x, y);
    }

    this.refreshCell = function (x, y) {
        this.syncGrid(x, y);
        this.drawCell(x, y);
    };

    this.onTouchOrMouseDown =(event)=> {
        let cell = this.getCellAtEventPosition(event);

        if (cell === null) {
            return true;
        }
        if(typeof this.drawingcell == 'undefined'){
            this.drawingcell = {};
        }
        this.drawingcell.start = cell;
        this.drawingcell.color = this.switchCellColor(this.model.cell(cell.x, cell.y), this.config.color.background, this.config.color.default);
        this.model.cell(cell.x, cell.y, 'color', this.drawingcell.color);
        this.refreshCell(cell.x, cell.y);
        return false;
    }

    this.switchCellColor = function(cell, changecolor, defaultcolor){
        if(cell == undefined){
            return this.config.color.all[defaultcolor];
        }
        return cell.color == this.config.color.all[changecolor]? this.config.color.all[defaultcolor]:this.config.color.all[changecolor];
    }

    this.onTouchOrMouseUp =(event)=> {
        this.drawingcell = undefined;
    }

    this.onTouchOrMouseMove =(event)=> {
        let cell = this.getCellAtEventPosition(event);
        if (cell === null || typeof this.drawingcell == 'undefined') {
            return false;
        }
        if (this.drawingcell.start) {
            this.drawingcell.end = cell;
            this.model.cell(cell.x, cell.y, 'color', this.drawingcell.color);
            this.refreshCell(cell.x, cell.y);
        }
        return false;
    }

    this.bindEvents = function (divid) {
        if(this.supportTouch){
            $(divid).on('touchstart', this.onTouchOrMouseDown);
            $(divid).on('touchend'  , this.onTouchOrMouseUp);
            $(document).on('touchmove', this.onTouchOrMouseMove);
        }else{
            $(divid).bind('mousedown', this.onTouchOrMouseDown);
            $(divid).bind('mouseup'  , this.onTouchOrMouseUp);
            $(document).bind('mousemove', this.onTouchOrMouseMove);
        }
    };

    this.autoConfigSize =function(divid) {
        if(typeof divid !== 'undefined'){
            this.divid = divid;
        }
        let left = $(this.divid).get(0).offsetLeft;
        let top  = $(this.divid).get(0).offsetTop;
        this.config.width = Math.floor($(window).width() - (left * 2));
        this.config.height = Math.floor($(window).height() - (top * 2));
        this.model.setSize(this.config.width, this.config.height);
    };

    this.drawCell = function(x, y) {
        let d      = this.draw;
        let offset = this.config.cellSize + this.config.borderWidth;
        let px = this.config.borderWidth + x * offset;
        let py = this.config.borderWidth + y * offset;
        let grade = undefined;
        if(this.model.cell(x, y) != undefined){
            d.fillStyle = this.model.cell(x, y, 'color');
            grade = this.model.cell(x, y, "grade");
        }else{
            d.fillStyle = this.config.color.all[this.config.color.background];
        }
        d.fillRect(px, py, this.config.cellSize, this.config.cellSize);
        if(this.gradDraw != undefined && grade != undefined){
            this.gradDraw(d, px, py, this.config.cellSize, this.config.cellSize, grade);
        }
    };

    this.makeCanvas = function() {
        let canvas = $('<canvas id="grid">');
        this.canvas = canvas.get(0);
        return this.canvas;
    };

    this.clearCanvas = function(canvas) {
        if(typeof canvas == 'undefined'){
            canvas = this.canvas;
        }
        if(typeof this.draw == 'undefined'){
            this.draw = canvas.getContext('2d');
        }
        this.draw.fillStyle = this.config.color.all[this.config.color.border];
        this.draw.fillRect(0, 0, this.config.width, this.config.height);
    }

    this.setCanvasSize = function(canvas) {
        if(typeof canvas == 'undefined'){
            canvas = this.canvas;
        }
        canvas.setAttribute("width",  this.config.width);
        canvas.setAttribute("height", this.config.height);
        this.clearCanvas(canvas);
    }

    this.drawGrid = function(force) {
        let x, y;
        if(force){
            this.snapShot(this.grid());
        }
        for (x = 0; x < this.grid().width; ++x) {
            for (y = 0; y < this.grid().height; ++y) {
                if (force || !this.grid().isSameCell(x, y, this.snapShot())) {
                    this.syncGrid(x, y);
                    this.drawCell(x, y);
                }
            }
        }
    };
}

CanvasView.prototype = new View();
CanvasView.prototype.constructor = CanvasView;
function CanvasView (divid, config) {
    this.divid = divid;
    CanvasView.prototype.loadConfig(config);
    CanvasView.prototype.bindEvents(divid);

    this.display = function () {
        let canvas = CanvasView.prototype.makeCanvas();
        CanvasView.prototype.autoConfigSize(divid);
        CanvasView.prototype.setCanvasSize(canvas);        
        CanvasView.prototype.drawGrid(true);
        $(this.divid).append(canvas);
    };

    this.setCellSize = function (px) {
        this.cellSize = px;
        CanvasView.prototype.setCanvasSize();
        CanvasView.prototype.drawGrid(true);
    };

    this.refreshGrid = function () {
        let force = false;
        if (!CanvasView.prototype.grid().isSame(CanvasView.prototype.snapShot())) {
            CanvasView.prototype.setCanvasSize();
            force = true;
        }
        CanvasView.prototype.drawGrid(force);
    };

    this.setOnResizeListener = function(listener){
        CanvasView.prototype.setOnResizeListener(listener);
    };

    this.bindModel = function(model){
        CanvasView.prototype.setModel(model);
        CanvasView.prototype.autoConfigSize(this.divid);
        model.onBind(CanvasView.prototype);
    };
}